package org.melanesia.sql;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.melanesia.beans.BeanUtils;
import org.melanesia.converters.JavaToJavaConverter;
import org.melanesia.converters.JavaToJdbcConverter;

/**
 * Abstract base type for sqlBuilder objects meta-data. Basically, this class is
 * a base for other classes storing Query / Call input/output parameters,
 * converters etc.
 *
 * @author marcin.kielar
 */
class QueryMetaData {

    /** Input parameter map. */
    private final Map<String, InputParameter> inputParameters = new HashMap<String, InputParameter>();

    /** Property path map. */
    private final Map<String, String> propertyPaths = new HashMap<String, String>();


    /** Ignored results (columns or output parameters). */
    private final Set<String> ignoredResults = new HashSet<String>();

    /** Explicit column converters. */
    private final Map<String, JavaToJavaConverter> explicitConverters = new HashMap<String, JavaToJavaConverter>();

    /**
     * Stores input parameter value.
     *
     * @param name
     *            parameter name
     * @param value
     *            parameter value
     */
    public final void setInputParameter(final String name, final Object value) {
        inputParameters.put(name, new InputParameter(name, value));
    }

    /**
     * Stores input parameter value.
     *
     * @param name
     *            parameter name
     * @param value
     *            parameter value
     * @param converter
     *            java to jdbc converter that should be used to convert given
     *            value to jdbc type
     */
    public final void setInputParameter(final String name, final Object value, final JavaToJdbcConverter converter) {
        inputParameters.put(name, new InputParameter(name, value, converter));
    }

    /**
     * Tells melanesia to ignore given result (column or output parameter) from the result set, during mapping to result bean.
     * For such result, no property accessor is looked up, and no actions are taken to map the column to the bean's property.
     *
     * @param columnName column name to ignore
     */
    public final void setIgnoredResult(final String columnName) {
        ignoredResults.add(columnName);
    }

    /**
     * Returns true if column was earlier marked as ignored.
     * @param columnName ignored column name
     * @return true if the column is to be ignored, false otherwise
     */
    public final boolean isResultIgnored(final String columnName) {
        return !ignoredResults.isEmpty() && ignoredResults.contains(columnName);
    }

    /**
     * Registers an explicit converter for a column.
     * @param columnName name of the column
     * @param converter converter
     */
    public final void setColumnConverter(final String columnName, final JavaToJavaConverter converter) {
        explicitConverters.put(columnName, converter);
    }

    /**
     * Returns prevoiusly registered converter for a column.
     * @param columnName column name
     * @return converter for the column
     */
    public final JavaToJavaConverter getColumnConverter(final String columnName) {
        return explicitConverters.get(columnName);
    }

    /**
     * Returns parameter names collection.
     *
     * @return collection of parameter names
     */
    final Collection<String> getInputParameterNames() {
        return inputParameters.keySet();
    }

    /**
     * Returns parameter value (or null if parameter of this name was not
     * previously stored).
     *
     * @param name
     *            parameter name
     * @return parameter value or null
     */
    final InputParameter getInputParameter(final String name) {
        return inputParameters.get(name);
    }

    /**
     * Stores property path for given result name.
     * This is useful, when there is an universal query used to populate many different java beans.
     *
     * Also, this can be helpful with composite properties (see {@link org.melanesia.beans.Setter#set(Object, Object, Map)}).
     * Many databases (ex. Oracle) have 30 character limit for the name of the column / alias returned from SQL.
     * When this is a case, it may be impossible to build long property paths (ex. someBean.anotherBean.yetAnotherBean.longNamedProperty),
     * and thus, to use composite property binding. In such cases, {@code setPropertyPathForResult} solves the problem.
     *
     * @param resultName name of the SQL column or output parameter
     * @param propertyPath property path to access java bean property
     */
    final void setPropertyPathForResult(final String resultName, final String propertyPath) {
        propertyPaths.put(resultName, propertyPath);
    }

    /**
     * Return proviously stored property path for given result name.
     *
     * @{see {@link #setPropertyPathForResult(String, String)}
     * @param resultName name of the SQL column or output parameter
     * @return property path, or if not set - resultName itself
     */
    final String getPropertyPathForResult(final String resultName) {
        return BeanUtils.nvl(propertyPaths.get(resultName), resultName);
    }

}
